﻿#region using
using System;
using System.IO;
using System.Collections.Generic;
using System.Security.AccessControl;
using System.Security.Principal;
using AifMessageHeaderEditor.Lib.Win32;
using AifMessageHeaderEditor.Lib.Data.File;
#endregion

namespace AifMessageHeaderEditor.Lib.File
{

    public abstract class FileBase
    {
        #region members and consts

        private FileData _xmlFile = new FileData();

        public FileData XmlFile
        {
            get { return _xmlFile; }
        }

        #endregion

        #region methods : file

        private string GetOwnershipFromFile(string filename)
        {
            if (filename == null)
            {
                throw new ArgumentNullException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }
            if (filename == string.Empty)
            {
                throw new ArgumentException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }
            if (! System.IO.File.Exists(filename))
            {
                throw new FileNotFoundException("The file: " + filename + " does not exist");
            }

            FileSecurity fileSecurity = System.IO.File.GetAccessControl(filename);
            string username = fileSecurity.GetOwner(typeof(NTAccount)).ToString();

            return username;
        }

        private void SetOwnershipToFile(string filename, string username)
        {
            if (filename == null)
            {
                throw new ArgumentNullException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }
            if (filename == string.Empty)
            {
                throw new ArgumentException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }
            if (! System.IO.File.Exists(filename))
            {
                throw new FileNotFoundException("The file: " + filename + " does not exist");
            }
            if (username == null)
            {
                throw new ArgumentNullException(AppCommon.FILE_EXCEPTION_NOUSERNAME);
            }
            if (username == string.Empty)
            {
                throw new ArgumentException(AppCommon.FILE_EXCEPTION_NOUSERNAME);
            }

            //default .net does only allow current user to be assigned as file-owner...
            TokenManipulator.AddPrivilege(TokenManipulator.SE_RESTORE_NAME);
            FileSecurity fileSecurity = System.IO.File.GetAccessControl(filename);
            NTAccount account = new NTAccount(Environment.UserDomainName, username);
            //this would probably be a little bit too much...
            //fileSecurity.AddAccessRule(new
            //    FileSystemAccessRule(account,
            //        FileSystemRights.FullControl,
            //        AccessControlType.Allow));
            fileSecurity.SetOwner(account);

            try
            {
                System.IO.File.SetAccessControl(filename, fileSecurity);
            }
            catch (InvalidOperationException ioEx)
            {
                throw new Exception(
                    "On some Windows OS, the fileOwner can only be set " +
                    "to the current logged on user",
                    ioEx);
            }
            catch (UnauthorizedAccessException uaEx)
            {
                throw new Exception("Make sure that the user/usergroup has file has" +
                "'Take Ownership'-permissions (File/Properties/Security/Advanced'", uaEx);
            }
            finally
            {
                TokenManipulator.RemovePrivilege(TokenManipulator.SE_RESTORE_NAME);
            }
        }

        private void SetOwnershipToFiles(string username)
        {
            if (username == null)
            {
                throw new ArgumentNullException(AppCommon.FILE_EXCEPTION_NOUSERNAME);
            }
            if (username == string.Empty)
            {
                throw new ArgumentException(AppCommon.FILE_EXCEPTION_NOUSERNAME);
            }

            try
            {
                MessageFile.Instance.SetOwnershipToFile(_xmlFile.SourceFilename, username);
            }
            catch (IdentityNotMappedException iEx)
            {
                throw new Exception(AppCommon.AIF_USER_UNKNOWN + iEx.Message, iEx);
            }
            catch (Exception ex)
            {
                throw new Exception(AppCommon.FILE_EXCEPTION_SOURCE + ex.Message, ex);
            }

            try
            {
                MessageFile.Instance.SetOwnershipToFile(_xmlFile.DestinationDirectory +
                    "\\" + Path.GetFileName(_xmlFile.SourceFilename), 
                    username);
            }
            catch (Exception ex)
            {
                throw new Exception(AppCommon.FILE_EXCEPTION_DESTINATION + ex.Message, ex);
            }

            _xmlFile.FileOwnerName = username;
            _xmlFile.FileOwner = GetOwnershipFromFile(_xmlFile.SourceFilename);

            NotifyChanges();
        }

        private void WriteDocument(string filename)
        {
            if (filename == null)
            {
                throw new ArgumentNullException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }
            if (filename == string.Empty)
            {
                throw new ArgumentException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }

            MessageFile.Instance.MessageFileInformation.SaveXmlMessage(filename);
            _xmlFile.DestinationDirectory = Path.GetDirectoryName(filename);
            SetOwnershipToFiles(_xmlFile.FileOwnerName);

        }

        #endregion

        #region XmlDocument

        protected abstract void NotifyChanges();

        /// <summary>
        /// Loads the Xml-file, validates it against the message.xsd 
        /// and verifies if the values are not empty strings
        /// </summary>
        /// <param name="filename">the full-filename</param>
        /// <returns>true if the document is valid</returns>
        public bool LoadDocument(string filename)
        {
            if (filename == null)
            {
                throw new ArgumentNullException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }
            if (filename == string.Empty)
            {
                throw new ArgumentException(AppCommon.FILE_EXCEPTION_NOFILENAME);
            }
            if (!System.IO.File.Exists(filename))
            {
                throw new FileNotFoundException(string.Format("The file: {0} does not exist", filename));
            }

            _xmlFile.SourceFilename = filename;

            MessageFile.Instance.MessageFileInformation.LoadXmlMessage(filename);
            bool isValid = ValidateFile(filename);

            if (_xmlFile.DestinationDirectory == null || _xmlFile.DestinationDirectory == string.Empty)
            {
                _xmlFile.DestinationDirectory = Path.GetDirectoryName(_xmlFile.SourceFilename);
            }

            _xmlFile.FileOwner = GetOwnershipFromFile(filename);
            _xmlFile.FileOwnerName = _xmlFile.FileOwner.Split('\\')[1];

            NotifyChanges();

            return isValid;
        }

        public void WriteDocument()
        {
            WriteDocument(_xmlFile.DestinationDirectory + "\\" + Path.GetFileName(_xmlFile.SourceFilename));
        }

        /// <summary>
        /// Sets the message to an empty instance
        /// </summary>
        public void ResetDocument()
        {
            _xmlFile.DestinationDirectory = null;
            _xmlFile.FileOwner = null;
            _xmlFile.FileOwnerName = null;
            _xmlFile.SourceFilename = null;

            MessageFile.Instance.MessageFileInformation.ResetMessage();

            NotifyChanges();
        }

        #endregion

        #region Validation 

        protected bool _isValid = true;
        protected List<String> _messageList = new List<String>();

        public bool IsValid
        {
            get { return _isValid; }
        }

        public List<String> ValidationMessageList
        {
            get
            {
                return _messageList;
            }
        }

        public abstract bool ValidateFile(string filename);

        #endregion
    }
}